package com.epi.two;

import javax.script.Bindings;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

/**
 * @author dxy
 * @version 1.0
 */
public class ScriptDemo {

    public static void main(String[] args) throws Exception {
//        engineDemo();
//        engineExampleDemo();
//        compilerExampleDemo();
    }

    private static void compilerExampleDemo() throws Exception {
        compilerDemo(null,null);
        String className = "PromoCalculator";
        String sourceCode = ""
            + "public class PromoCalculator {\n"
            + "    public double calc(double price, int quantity) {\n"
            + "        double total = price * quantity;\n"
            + "        if (total >= 300) {\n"
            + "            total = total - 50;\n"
            + "        } else if (total >= 200) {\n"
            + "            total = total * 0.9;\n"
            + "        }\n"
            + "        return total;\n"
            + "    }\n"
            + "}";
        compilerDemo(className,sourceCode);
    }

    private static void compilerDemo(String className, String sourceCode) throws Exception {
        // 1. 获取系统Java编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) {
            System.out.println("JDK编译器不可用（可能是JRE环境）");
            return;
        }

        // 2. 诊断收集器，用于获取编译错误信息
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();

        // 3. 文件管理器
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null);

        // 4. 动态构造Java源代码（这里是字符串形式）
        className = Objects.requireNonNullElse(className, "HelloWorld");
        sourceCode = Objects.requireNonNullElse(sourceCode, "public class HelloWorld { public String greet() { return \"Hello, dynamic compile!\"; } }");

        // 5. 创建JavaFileObject，使用SimpleJavaFileObject实现内存文件
        JavaFileObject javaFileObject = new InMemoryJavaFileObject(className, sourceCode);

        // 指定编译输出目录，比如当前目录下的 out 目录
        File outputDir = new File("target/classes/com/epi/two/");
        outputDir.mkdirs();
        // 设置编译参数，-d 指定输出目录
        Iterable<String> options = Arrays.asList("-d", outputDir.getAbsolutePath());

        // 6. 编译任务
        Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(javaFileObject);
        JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits);

        // 7. 执行编译
        boolean success = task.call();
        if (success) {
            System.out.println("编译成功");
            // 可以动态加载类进行反射调用，略
        } else {
            diagnostics.getDiagnostics().forEach(d -> {
                System.out.println(d.getKind() + ":: " + d.getMessage(null));
            });
        }

        fileManager.close();

        // 用 URLClassLoader 加载编译后的类
        try (URLClassLoader classLoader = new URLClassLoader(new URL[]{outputDir.toURI().toURL()})) {
            Class<?> clazz = Class.forName(className, true, classLoader);
            Object instance = clazz.getDeclaredConstructor().newInstance();
            Method method;
            Object result;
            if (className == "HelloWorld") {
                method = clazz.getMethod("greet");
                result = method.invoke(instance);
            } else {
                method = clazz.getMethod("calc", double.class,int.class);
                result = method.invoke(instance,100,3);
            }
            System.out.println("Result: " + result);
        }
    }

    // 内存Java文件对象，用来存放源码字符串
    static class InMemoryJavaFileObject extends SimpleJavaFileObject {
        private final String sourceCode;

        protected InMemoryJavaFileObject(String className, String sourceCode) {
            super(java.net.URI.create("string:///" + className.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
            this.sourceCode = sourceCode;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return sourceCode;
        }
    }

    /**
     * 业务逻辑可动态替换
     * 独立修改逻辑脚本
     * 实现快速迭代
     *
     * @throws ScriptException
     */
    private static void engineExampleDemo() throws ScriptException {
        //        模拟订单数据
        double price = 100.0;
        int quantity = 3;

        // 促销脚本（可以从数据库或配置文件动态加载）
        String promoScript = ""
            + "var total = price * quantity;\n"
            + "// 满300减50\n"
            + "if (total >= 300) {\n"
            + "    total = total - 50;\n"
            + "}\n"
            + "// 满200打9折\n"
            + "else if (total >= 200) {\n"
            + "    total = total * 0.9;\n"
            + "}\n"
            + "total;"; // 返回最终金额

        // 创建脚本引擎
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");

        // 传入变量
        engine.put("price", price);
        engine.put("quantity", quantity);

        // 执行脚本
        Object result = engine.eval(promoScript);

        System.out.println("优惠后总价: " + result);
    }

    private static void engineDemo() throws ScriptException {
        ScriptEngineManager manager = new ScriptEngineManager();
//        manager.getEngineFactories().stream().forEach(System.out::println);
        ScriptEngine engine = manager.getEngineByName("nashorn");
        ScriptEngine engine1 = manager.getEngineByMimeType("application/javascript");
        ScriptEngine engine2 = manager.getEngineByExtension("js");
        if (engine == null || engine1 == null || engine2 == null) {
            System.out.println("JavaScript engine not found");
        }

        System.out.println("Result: " + engine.eval("1 + 2 * 3"));

        // 引擎作用域
        engine.put("x", 1);
        engine.put("y", 1);
        // Bindings
        Bindings bindings = engine.createBindings();
        bindings.put("x", 10);
        bindings.put("y", 5);

        System.out.println(engine.eval("x * y + 3", bindings)); // 53
        System.out.println(engine.eval("x * y + 3")); // 4


        engine.eval("print('hello');");
    }
}
